package com.navjagpal.notes;

import java.io.IOException;
import java.net.MalformedURLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import androidx.gdata.Category;
import androidx.gdata.documents.DocumentsServer;
import androidx.gdata.documents.Entry;
import androidx.gdata.spreadsheets.GSX;
import androidx.gdata.spreadsheets.ListCollection;
import androidx.gdata.spreadsheets.ListEntry;
import androidx.gdata.spreadsheets.Spreadsheet;
import androidx.gdata.spreadsheets.SpreadsheetCollection;
import androidx.gdata.spreadsheets.SpreadsheetsServer;
import androidx.gdata.spreadsheets.Worksheet;
import androidx.gdata.spreadsheets.WorksheetCollection;

/**
 * This service is responsible for synchronizing our Notes with Google Spreadsheets.
 * 
 * @author nav
 */
public class SyncService extends Service {
 
  private static final String LIST_PREFIX = "XNOTES_";
  private static final int NOTIFICATION_SYNC_ID = 1;
  private NotificationManager mNotificationManager;
  
  private ISyncService.Stub mBinder = new ISyncService.Stub() {
    public int getLastSyncTime() {
      return -1;
    }
    
    private void showSyncNotification() {
      Notification notification = new Notification(
          R.drawable.icon, getText(R.string.sync_ticker),
          System.currentTimeMillis());
      Intent intent = new Intent(SyncService.this, Notes.class);
      PendingIntent pi = PendingIntent.getActivity(SyncService.this, 0, intent, 0);
      notification.setLatestEventInfo(
          getApplicationContext(), getText(R.string.app_name),
          getText(R.string.sync_inprogress), pi);
      mNotificationManager.notify(NOTIFICATION_SYNC_ID, notification);
    }
      
    private void cancelSyncNotification() {
      mNotificationManager.cancel(NOTIFICATION_SYNC_ID);
    }
    
    public void startSync() {
      new Thread() {
        public void run() {
          try {
            showSyncNotification();
            sync();
            cancelSyncNotification();
            mNotificationManager.cancel(NOTIFICATION_SYNC_ID);
          } catch (IOException e) {
            // TODO: Update the sync notification to include an error mesage.
          } 
        }
      }.start();
    }
  };

  @Override
  public IBinder onBind(Intent intent) {
    if (ISyncService.class.getName().equals(intent.getAction())) {
      return mBinder;
    }
    return null;
  }
  
  protected void sync() throws IOException {
    syncNewSpreadsheets();
    syncExistingSpreadsheets();
    syncNewLists();
  }
  
  public void onCreate() {
    super.onCreate();
    mNotificationManager = (NotificationManager) getSystemService(
        Context.NOTIFICATION_SERVICE);
  }
  
  private void syncNewSpreadsheets() throws IOException {
    SpreadsheetsServer server = NotesApplication.getInstance().getSpreadsheetsServer();
    SpreadsheetCollection collection = server.getSpreadsheetCollection();
    for (Spreadsheet entry : collection.getEntries()) {
      if (entry.getTitle().startsWith(LIST_PREFIX)) {
        syncNewSpreadsheet(entry);
      }
    }
  }
  
  private void syncNewLists() {
    Cursor c = getContentResolver().query(
        NotesProvider.List.CONTENT_URI,
        new String[] {NotesProvider.List.Columns._ID,
            NotesProvider.List.Columns.NAME},
        NotesProvider.List.Columns.LAST_MODIFIED + ">" +
        NotesProvider.List.Columns.LAST_SYNC,
        null, null);
    final int idIndex = c.getColumnIndex(NotesProvider.List.Columns._ID);
    final int nameIndex = c.getColumnIndex(NotesProvider.List.Columns.NAME);
    while (c.moveToNext()) {
      syncNewList(c.getLong(idIndex), c.getString(nameIndex));
    }
    c.close();
  }
  
  private void syncNewList(long id, String name) {
    DocumentsServer server = NotesApplication.getInstance().getDocumentsServer();
    Category category = new Category(
        "http://schemas.google.com/g/2005#kind",
        "http://schemas.google.com/docs/2007#spreadsheet");
    Entry entry = new Entry(LIST_PREFIX + name, category, null);
    try {
      Entry newEntry = server.uploadDocument(entry);
      
      updateListSyncTime(id);
    } catch (IOException e) {
      Log.e("Notes", "Problem creating new list " + name + e.toString());
    } 
  }
  
  private void updateListSyncTime(long id) {
    ContentValues values = new ContentValues();
    values.put(NotesProvider.List.Columns.LAST_SYNC,
        System.currentTimeMillis() / 1000);
    getContentResolver().update(
        NotesProvider.List.CONTENT_URI,
        values,
        NotesProvider.List.Columns._ID + "=?",
        new String[] {"" + id});
  }
  
  private void syncExistingSpreadsheets() {
    addNewLocalNotesToRemoteSpreadsheets();
    pushLocalNoteChangesToRemoteSpreadsheets();
  }
  
  private void pushLocalNoteChangesToRemoteSpreadsheets() {
    Cursor noteCursor = getContentResolver().query(
        NotesProvider.Note.CONTENT_URI,
        null,
        NotesProvider.Note.Columns.LAST_MODIFIED + " > " +
        NotesProvider.Note.Columns.LAST_SYNC,
        null, null);
    int noteIdIndex = noteCursor.getColumnIndex(NotesProvider.Note.Columns._ID);
    int gdataIdIndex = noteCursor.getColumnIndex(NotesProvider.Note.Columns.GDATA_ID);
    int titleIndex = noteCursor.getColumnIndex(NotesProvider.Note.Columns.TITLE);
    int contentsIndex = noteCursor.getColumnIndex(NotesProvider.Note.Columns.CONTENTS);
    int editUrlIndex = noteCursor.getColumnIndex(NotesProvider.Note.Columns.EDIT_URL);
    SpreadsheetsServer server = NotesApplication.getInstance().getSpreadsheetsServer();
    while (noteCursor.moveToNext()) {
      GSX[] gsx = new GSX[2];
      gsx[0] = new GSX(NotesProvider.Note.Columns.TITLE,
          noteCursor.getString(titleIndex));
      gsx[1] = new GSX(NotesProvider.Note.Columns.CONTENTS,
          noteCursor.getString(contentsIndex));
      String gdataId = noteCursor.getString(gdataIdIndex);
      ListEntry listEntry = new ListEntry(gdataId, null, null, null, gsx);
      String editUrl = noteCursor.getString(editUrlIndex);
      try {
        server.putListEntry(editUrl, listEntry);
        long noteId = noteCursor.getLong(noteIdIndex);
        updateNoteSyncTime(noteId, editUrl);
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }
  
  private void addNewLocalNotesToRemoteSpreadsheets() {
    Log.i("Notes", "Adding new local notes to remote spreadsheets");
    Cursor listCursor = getContentResolver().query(
        NotesProvider.List.CONTENT_URI,
        new String[] {NotesProvider.List.Columns._ID, NotesProvider.List.Columns.POST_URL},
        null, null, null);
    int listIdIndex = listCursor.getColumnIndex(NotesProvider.List.Columns._ID);
    int postUrlIndex = listCursor.getColumnIndex(NotesProvider.List.Columns.POST_URL);
    SpreadsheetsServer server = NotesApplication.getInstance().getSpreadsheetsServer();
    while (listCursor.moveToNext()) {
      long listId = listCursor.getLong(listIdIndex);
      Log.i("Notes", "Processing list " + listId);
      String postUrl = listCursor.getString(postUrlIndex);
      Cursor noteCursor = getContentResolver().query(
          NotesProvider.Note.CONTENT_URI,
          new String[] {
              NotesProvider.Note.Columns._ID,
              NotesProvider.Note.Columns.TITLE, NotesProvider.Note.Columns.CONTENTS},
          NotesProvider.Note.Columns.LIST_ID + "=? AND " +
          NotesProvider.Note.Columns.LAST_SYNC + "=0",
          new String[] {"" + listId}, null);
      int noteIdIndex = noteCursor.getColumnIndex(NotesProvider.Note.Columns._ID);
      int titleIndex = noteCursor.getColumnIndex(NotesProvider.Note.Columns.TITLE);
      int contentsIndex = noteCursor.getColumnIndex(NotesProvider.Note.Columns.CONTENTS);
      int numNewNotes = 0;
      while (noteCursor.moveToNext()) {
        numNewNotes++;
        GSX[] gsx = new GSX[2];
        gsx[0] = new GSX(NotesProvider.Note.Columns.TITLE,
            noteCursor.getString(titleIndex));
        gsx[1] = new GSX(NotesProvider.Note.Columns.CONTENTS,
            noteCursor.getString(contentsIndex));
        ListEntry listEntry = new ListEntry(null, null, null, null, gsx);
        try {
          ListEntry newEntry = server.postListEntry(postUrl, listEntry);
          // Update the sync time for this entry.
          long noteId = noteCursor.getLong(noteIdIndex);
          updateNoteSyncTime(noteId, newEntry.getEditUrl());
        } catch (IOException e) {
          // Don't consider this item added.
        }
      }
      Log.i("Notes", "Added " + numNewNotes + " new notes");
      noteCursor.close();
    }
    listCursor.close();
  }
  
  private void updateNoteSyncTime(long noteId, String editUrl) {
    ContentValues values = new ContentValues();
    values.put(NotesProvider.Note.Columns.EDIT_URL, editUrl);
    values.put(NotesProvider.Note.Columns.LAST_SYNC,
        System.currentTimeMillis() / 1000);
    getContentResolver().update(
        NotesProvider.Note.CONTENT_URI,
        values,
        NotesProvider.Note.Columns._ID + "=?",
        new String[] {"" + noteId});
  }

  private void syncNewSpreadsheet(Spreadsheet spreadsheet) throws
  MalformedURLException, IOException {
    String name = spreadsheet.getTitle().substring(
        LIST_PREFIX.length());
    SpreadsheetsServer server = NotesApplication.getInstance().getSpreadsheetsServer();
    WorksheetCollection collection = server.getWorksheetCollection(
        spreadsheet);
    Worksheet[] entries = collection.getEntries();
    if (entries == null || entries.length == 0) {
      return;
    }
    
    Worksheet worksheet = entries[0];
    long listId = getListId(worksheet.getListUrl());
    
    /* Only process this spreadsheet if it is new. A different process handles
     * merging of existing spreadsheets.
     */
    if (listId == -1) {
      ListCollection listCollection = server.getListCollection(worksheet);
      listId = createList(name, worksheet.getListUrl(), listCollection.getPostUrl());
      addWorksheetToList(listCollection, listId);
      updateListSyncTime(listId);
    } 
  }
  
  private long getListId(String listUrl) {
    Cursor c = getContentResolver().query(
        NotesProvider.List.CONTENT_URI,
        new String[] {NotesProvider.List.Columns._ID},
        NotesProvider.List.Columns.LIST_URL + "=?",
        new String[] {listUrl}, null);
    long id = -1;
    if (c.moveToFirst()) {
      id = c.getLong(c.getColumnIndex(NotesProvider.List.Columns._ID));
    }
    c.close();
    return id;
  }
  
  
  private long createList(String name, String listUrl, String postUrl) {
    ContentValues values = new ContentValues();
    values.put(NotesProvider.List.Columns.NAME, name);
    values.put(NotesProvider.List.Columns.LIST_URL, listUrl);
    values.put(NotesProvider.List.Columns.POST_URL, postUrl);
    Uri uri = getContentResolver().insert(
        NotesProvider.List.CONTENT_URI, values);
    return ContentUris.parseId(uri);
  }
  
  private void addWorksheetToList(ListCollection listCollection, long listId)
    throws MalformedURLException, IOException {
    Log.i("Notes", "Adding worksheet to list");
    
    /* Now iterate through the rows and add them to the list. */
    for (ListEntry listEntry : listCollection.getEntries()) {
      GSX[] gsxEntries = listEntry.getGsx();
      String title = null;
      String contents = "";
      String editUrl = listEntry.getEditUrl();
      for (GSX gsx: gsxEntries) {
        Log.i("Notes", "Looking at " + gsx.getColumn() + " " + gsx.getContent());
        if (gsx.getColumn().toLowerCase().equals(
            NotesProvider.Note.Columns.TITLE)) {
          title = gsx.getContent();
        } else if (gsx.getColumn().toLowerCase().equals(
            NotesProvider.Note.Columns.CONTENTS)) {
          contents = gsx.getContent();
        } 
      }
      if (title != null) {
        Log.i("Notes", "Going to add to list " + listId);
        addNoteToList(listId, listEntry.getId(), title, contents, editUrl);
      }
    }
  }

  private Uri addNoteToList(long listId, String gdataId, String title, String contents,
      String editUrl) {
    ContentValues values = new ContentValues();
    values.put(NotesProvider.Note.Columns.TITLE, title);
    values.put(NotesProvider.Note.Columns.CONTENTS, contents);
    values.put(NotesProvider.Note.Columns.LIST_ID, listId);
    values.put(NotesProvider.Note.Columns.GDATA_ID, gdataId);
    long lastModified = System.currentTimeMillis() / 1000;
    values.put(NotesProvider.Note.Columns.LAST_SYNC, lastModified);
    values.put(NotesProvider.Note.Columns.LAST_MODIFIED, lastModified);
    values.put(NotesProvider.Note.Columns.EDIT_URL, editUrl);
    return getContentResolver().insert(NotesProvider.Note.CONTENT_URI, values);
  }
  
}
